/*******************************************************************************
 * Copyright 2010 Simon Mieth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package cn.kkdlk.parse.math;


public class ParametricPlane {
    protected Point3D base;
    protected Vector directionX;
    protected Vector directionY;
    protected Vector normal;

    /**
     * @param basePoint  The base point of this plane
     * @param directionX the x direction of this plane
     * @param directionY the y direction of this plane
     * @param normal     the normal direction of this plane
     */
    public ParametricPlane(Point3D basePoint, Vector directionX,
                           Vector directionY, Vector normal) {
        this.base = basePoint;
        this.directionX = directionX;
        this.directionY = directionY;
        this.normal = normal;
    }

    /**
     * @param basePoint  The base point of this plane
     * @param directionX the x direction of this plane
     * @param directionY the y direction of this plane
     */
    public ParametricPlane(Point3D basePoint, Vector directionX, Vector directionY) {
        this(basePoint, directionX, directionY,
                MathUtils.normalize(MathUtils.crossProduct(directionX, directionY)));
    }

    /**
     * Generates a plane with the base point and uses the vector from base point
     * to b as x direction. The y direction is generated with the cross product
     * of the normal with the x direction.
     *
     * @param basePoint
     * @param b
     * @param normal
     */
    public ParametricPlane(Point3D basePoint, Point3D b, Vector normal) {
        this(basePoint, MathUtils.normalize(MathUtils.getVector(basePoint, b)),
                MathUtils.normalize(MathUtils.crossProduct(normal,
                        MathUtils.normalize(MathUtils.getVector(basePoint, b)))),
                normal);
    }

    public ParametricPlane(Point3D basePoint, Point3D b, Point3D c) {
        this(basePoint, MathUtils.normalize(MathUtils.getVector(basePoint, b)),
                MathUtils.normalize(MathUtils.getVector(basePoint, c)));
    }

    public ParametricPlane(Extrusion e) {
        this(new Point3D(0.0, 0.0, 0.0), e.getDirectionX(), e.getDirectionY(),
                e.getNormal());
    }

    /**
     * Calculate the point in world coordinates for the given parameters
     *
     * @param x
     * @param y
     * @return
     */
    public Point3D getPoint(double x, double y) {
        Point3D p = new Point3D();
        p.setX(this.base.getX() + (this.directionX.getX() * x) +
                (this.directionY.getX() * y));
        p.setY(this.base.getY() + (this.directionX.getY() * x) +
                (this.directionY.getY() * y));
        p.setZ(this.base.getZ() + (this.directionX.getZ() * x) +
                (this.directionY.getZ() * y));

        return p;
    }

    public Point3D getPoint(Point3D point) {
        return getPoint(point.getX(), point.getY());
    }

    /**
     * Calculates the plane parameters of the given point relative to the base
     * point of the plane
     *
     * @param p
     * @return double[]{parameter x direction, parameter y direction}
     */
    public double[] getParameter(Point3D p) {
        double u = 0.0;
        double v = (this.directionX.getY() * this.directionY.getX()) -
                (this.directionX.getX() * this.directionY.getY());

        if (v != 0.0) {
            v = ((p.getY() * this.directionY.getX()) -
                    (this.base.getY() * this.directionY.getX()) -
                    (this.directionY.getY() * p.getX()) +
                    (this.base.getX() * this.directionY.getY())) / v;
        }

        if (this.directionY.getX() != 0.0) {
            u = (p.getX() - this.base.getX() - (this.directionX.getX() * v)) / this.directionY.getX();
        } else if (this.directionY.getY() != 0.0) {
            u = (p.getY() - this.base.getY() - (this.directionX.getY() * v)) / this.directionY.getY();
        } else if (this.directionY.getY() != 0.0) {
            u = (p.getZ() - this.base.getZ() - (this.directionX.getZ() * v)) / this.directionY.getZ();
        }

        return new double[]{v, u};
    }

    /**
     * Determines if the given point lies on the plane.
     *
     * @param p the point to determine
     * @return true if the point lies on the plane, otherwise false.
     */
    public boolean isOnPlane(Point3D p) {
        double[] para = this.getParameter(p);
        double v = this.base.getZ() + (this.directionX.getZ() * para[0]) +
                (this.directionY.getZ() * para[1]);

        if (!(Math.abs((p.getZ() - v)) < MathUtils.DISTANCE_DELTA)) {
            return false;
        }

        v = this.base.getY() + (this.directionX.getY() * para[0]) +
                (this.directionY.getY() * para[1]);

        if (!(Math.abs((p.getY() - v)) < MathUtils.DISTANCE_DELTA)) {
            return false;
        }

        v = this.base.getX() + (this.directionX.getX() * para[0]) +
                (this.directionY.getX() * para[1]);

        return Math.abs((p.getX() - v)) < MathUtils.DISTANCE_DELTA;
    }

    public Point3D getBasePoint() {
        return base;
    }

    public void setBasePoint(Point3D base) {
        this.base = base;
    }

    public Vector getDirectionX() {
        return directionX;
    }

    public void setDirectionX(Vector directionX) {
        this.directionX = directionX;
        this.normal = MathUtils.crossProduct(this.directionX, this.directionY);
        this.normal.normalize();
    }

    public Vector getDirectionY() {
        return directionY;
    }

    public void setDirectionY(Vector directionY) {
        this.directionY = directionY;
        this.normal = MathUtils.crossProduct(this.directionX, this.directionY);
        this.normal.normalize();
    }

    public Vector getNormal() {
        return normal;
    }
}
